// --- 常用工具类 ---

//Java的核心库提供了大量的现成的类供我们使用。本节我们介绍几个常用的工具类。

/* # Math
顾名思义，Math类就是用来进行数学计算的，它提供了大量的静态方法来便于我们实现数学计算：
*/

//求绝对值
Math.abs(-100); //100
Math.abs(-7.8); //7.8


//取最大或最小值：
Math.max(100, 99); // 100
Math.min(1.2, 2.3); // 1.2


//计算x^y次方：
Math.pow(2, 10); // 2的10次方=1024; pow = power n. 次方



//计算根号√x：
Math.sqrt(2); // 1.414...


//计算e^x次方：
Math.exp(2); // 7.389...


//计算以e为底的对数：
Math.log(4); // 1.386...


//计算以10为底的对数：
Math.log10(100); // 2


//三角函数：
Math.sin(3.14); // 0.00159...
Math.cos(3.14); // -0.9999...
Math.tan(3.14); // -0.0015...
Math.asin(1.0); // 1.57079...
Math.acos(1.0); // 0.0


//Math还提供了几个数学常量：
double pi = Math.PI; // 3.14159...
double e = Math.E; // 2.7182818...
Math.sin(Math.PI / 6); // sin(π/6) = 0.5


//生成一个随机数x，x的范围是0 <= x < 1：
Math.random(); // 0.53907... 每次都不一样



//如果我们要生成一个区间在[MIN, MAX)的随机数，可以借助Math.random()实现，计算如下：
public class Main {
	public static void main(String[] args) {
		double x = Math.random(); // x的范围是[0,1)

		double min = 10;
		double max = 50;

		double y = x*(max - min) + min; // y的范围是[10,50)
		long n = (long) y; // // n的范围是[10,50)的整数

		System.out.println(y);
		System.out.println(n);
	}
}
/*
有些童鞋可能注意到Java标准库还提供了一个StrictMath，它提供了和Math几乎一模一样的方法。

这两个类的区别在于，由于浮点数计算存在误差，不同的平台（例如x86和ARM）计算的结果可能不一致（指误差不同）.

因此，StrictMath保证所有平台计算结果都是完全相同的，而Math会尽量针对平台优化计算速度，所以，绝大多数情况下，使用Math就足够了。
*/





/* # Random

Random用来创建伪随机数。所谓伪随机数，是指只要给定一个初始的种子，产生的随机数序列是完全一样的。

要生成一个随机数，可以使用nextInt()、nextLong()、nextFloat()、nextDouble()： */
Random r = new Random();
r.nextInt(); // 2071575453,每次都不一样
r.nextInt(10); // 5,生成一个[0,10)之间的int
r.nextLong(); // 8811649292570369305,每次都不一样
r.nextFloat(); // 0.54335...生成一个[0,1)之间的float
r.nextDouble(); // 0.3716...生成一个[0,1)之间的double

/*
有童鞋问，每次运行程序，生成的随机数都是不同的，没看出伪随机数的特性来。

这是因为我们创建Random实例时，如果不给定种子，就使用系统当前时间戳作为种子，因此每次运行时，种子不同，得到的伪随机数序列就不同。

如果我们在创建Random实例时指定一个种子，就会得到完全确定的随机数序列：  */
import java.util.Random;

public class Main {
	public static void main(String[] args) {
		Random r = new Random(12345);

		for (int i=0; i<10; i++) {
			System.out.println(r.nextInt(100));
		}

		// 51, 80, 41, 28, 55...
	}
}
//前面我们使用的Math.random()实际上内部调用了Random类，所以它也是伪随机数，只是我们无法指定种子。

/* 种子的概念：

Random r = new Random(47);
    int a = r.nextInt(26);
    System.out.println(a);

想要获取一个范围内的随机数（例如26，随机数可能是0-25），首先需要一个种子（其实就是一个数值）。
    每个种子会对应这个范围内（0-26）的唯一的一个随机数。
    47这个种子在26这个范围内，所对应的随机数为24，所以每次随机得出的结果都为24.
    （注意：47在26这个范围内对应的是24，这个是死的，固定的，无论你执行多少次，它还是24）
    至于为什么种子47会对应24，这个涉及到java封装的算法，有兴趣可以深入了解。

 总之，new Random(47)里面的47表示产生随机数的一个种子，nextInt(26)表示随机数的范围，种子和范围是相关联的，一个种子对应一个范围内的一个固定的随机数，如果不填种子，则会默认取当前时间的毫秒数作为种子来生成随机数。
*/




 /* # SecureRandom

有伪随机数，就有真随机数。实际上真正的真随机数只能通过量子力学原理来获取，而我们想要的是一个不可预测的安全的随机数，SecureRandom就是用来创建安全的随机数的： */
SecureRandom sr = new SecureRandom();
System.out.println(sr.nextInt(100));

/*
SecureRandom无法指定种子，它使用RNG（random number generator）算法。JDK的SecureRandom实际上有多种不同的底层实现，有的使用安全随机种子加上伪随机数算法来产生安全的随机数，有的使用真正的随机数生成器。实际使用的时候，可以优先获取高强度的安全随机数生成器，如果没有提供，再使用普通等级的安全随机数生成器：  */
import java.util.Arrays;
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;

public class Main {
	public static void main(String[] args) {
		SecureRandom sr = null;

		try {
			sr = SecureRandom.getInstanceStrong(); //获取高强度安全随机数生成器
		} catch (NoSuchAlgorithmException e) {
			sr = new SecureRandom(); //获取普通强度的安全随机数生成器
		}

		byte[] buffer = new byte[16];
		sr.nextBytes(buffer); //用安全随机数填充buffer
		System.out.println(Arrays.toString(buffer));
			//输出安全随机数填充后的byte[] buffer -> String
	}
}
//SecureRandom的安全性是通过操作系统提供的安全的随机种子来生成随机数。这个种子是通过CPU的热噪声、读写磁盘的字节、网络流量等各种随机事件产生的“熵”。

//在密码学中，安全的随机数非常重要。如果使用不安全的伪随机数，所有加密体系都将被攻破。因此，时刻牢记必须使用SecureRandom来产生安全的随机数。



/* --- 实用工具类の小结 ---
Java提供的常用工具类有：

	Math：数学计算

	Random：生成伪随机数

	SecureRandom：生成安全的随机数

*/